`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2022/08/16 19:54:39
// Design Name: 
// Module Name: mcu_cpu_alu
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module mcu_cpu_alu(
    input en,
    input [2:0] opcode,
    input [2:0] funct,
    input [15:0] op1,
    input [15:0] op2,
    input [15:0] pc,
    input [15:0] imm,
    output reg out_en,
    output reg [15:0] out,
    
    output reg jmp_en,
    output reg [15:0] jmp_addr,
    
    output reg ld_en,
    output reg [15:0] ld_addr,
    output reg ld_word,
    output reg ld_ext,
    output reg ld_loc,
    
    output reg st_en,
    output reg [15:0] st_addr,
    output reg [15:0] st_data,
    output reg [1:0] st_mask
    );

// opcode 000: op rd,imm[signed]
// [add and or xor] [shl shr sar sub]
// not => xor rd,-1

// opcode 001: op rd,rs
// [add and or xor] [shl shr sar sub]

// opcode 010: op rd,[rs+imm]
// [lb lw lbu] [sb sw] [lea]
// lw/sw (rs + imm)[0] is ignored

// opcode 011: op rd,[pc+imm]
// [lb lw lbu] [sb sw] [lea]
// lw/sw (pc + imm)[0] is ignored

// opcode 100: [branch] op [pc+imm]
// jmp

// opcode 101: [branch] op [rd+imm]
// jmpr

// opcode 110: [branch] op rd,rs,(imm<<1)[signed target]
// beq bne blt bge bltu bgeu

reg [15:0] arith_rs;
reg [15:0] mem_addr;
reg cmp_tmp;

always @* begin
    out_en = 0;
    out = 0;
    
    jmp_en = 0;
    jmp_addr = 0;
    
    ld_en = 0;
    ld_addr = 0;
    ld_word = 0;
    ld_ext = 0;
    ld_loc = 0;
    
    st_en = 0;
    st_addr = 0;
    st_data = 0;
    st_mask = 0;
    
    if (en) begin
        case (opcode)
        3'b000,
        3'b001: begin
            arith_rs = (opcode[0] == 0) ? imm : op2 + imm;
            out_en = 1;
            (* full_case *)
            case (funct)
            3'b000: out = op1 + arith_rs; // add
            3'b001: out = op1 & arith_rs; // and
            3'b010: out = op1 | arith_rs; // or
            3'b011: out = op1 ^ arith_rs; // xor
            3'b100: out = op1 << arith_rs; // sll
            3'b101: out = op1 >> arith_rs; // srl
            3'b110: out = op1 >>> arith_rs; // sra
            3'b111: out = arith_rs; // mov
            endcase
        end
        3'b010,
        3'b011: begin
            mem_addr = ((opcode[0] == 0) ? op2 : pc) + imm;
            case (funct)
            3'b000: begin // lb
                ld_en = 1;
                ld_addr = mem_addr;
                ld_loc = mem_addr[0];
                
                ld_word = 0;
                ld_ext = 1;
            end
            3'b001: begin // lw
                ld_en = 1;
                ld_addr = mem_addr;
                ld_loc = mem_addr[0];
                
                ld_word = 1;
                ld_ext = 0;
            end
            3'b010: begin // lbu
                ld_en = 1;
                ld_addr = mem_addr;
                ld_loc = mem_addr[0];
                
                ld_word = 0;
                ld_ext = 0;
            end
            3'b011: begin // sb
                st_en = 1;
                st_addr = mem_addr;
                
                st_data = {2{op1[7:0]}};
                st_mask = mem_addr[0] ? 2'b10 : 2'b01;
            end
            3'b100: begin // sw
                st_en = 1;
                st_addr = mem_addr;
                
                st_data = op1;
                st_mask = 2'b11;
            end
            3'b101: begin // lea
                out_en = 1;
                out = mem_addr;
            end
            default: ;
            endcase
        end
        3'b100,
        3'b101: begin // jmp
            jmp_en = 1;
            jmp_addr = ((opcode[0] == 0) ? pc : op1) + imm;
        end
        3'b110: begin
            jmp_addr = pc + imm;
            case (funct[2:1])
            3'b00: begin // 0:beq 1:bne
                cmp_tmp = op1 == op2;
                jmp_en = funct[0] ^ cmp_tmp;
            end
            3'b01: begin // 0:blt 1:bge
                cmp_tmp = $signed(op1) < $signed(op2);
                jmp_en = funct[0] ^ cmp_tmp;
            end
            3'b10: begin // 0:bltu 1:bgeu
                cmp_tmp = $unsigned(op1) < $unsigned(op2);
                jmp_en = funct[0] ^ cmp_tmp;
            end
            default: jmp_en = 0;
            endcase
        end
        default: ;
        endcase
    end
end

endmodule
